﻿using System;
using System.Collections.Generic;
using Cinemachine;
using Gameplay.PVE.Utils;
using UnityEngine;
using Yoozoo.Core.Extensions.Engine;
using Yoozoo.Framework.Core;
using Yoozoo.Managers;

namespace Gameplay.PVE.Survivor
{
    public class UnitManager: BattleSingleton<UnitManager>
    {
        private Dictionary<int, List<BattleUnit>> unitMap = new Dictionary<int, List<BattleUnit>>();
        
        private Dictionary<int,BattleUnit> unitIdMap = new Dictionary<int, BattleUnit>();

        private int selfUnitId = 1000;
        private int enemyUnitId = 2000;
        private int environmentUnitId = 3000;
        
        private Dictionary<int,Action> unitHpChangeCallbacks = new Dictionary<int, Action>();
        private Dictionary<int,Action> unitPositionChangeCallbacks = new Dictionary<int, Action>();
        
		public override void Initialize()
        {
            
        }

        public override void Update()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    var unit = list[i];
                    try
                    {
                        unit.Update();
                        if (unit.isDead && (TimeManager.time - unit.deadTime > 6))
                        {
                            RemoveUnit(unit);
                            i--;
                        }
                    }
                    catch (Exception e)
                    {
                        BattleDebug.LogError(unit.unitId + " 单位报错 " + e.ToString() + " "+ e.StackTrace);
                    }
                }
            }

            battlePoint = BattleConstValue.DEFAULT_POINT;
            //battlePoints.Clear();
        }

        public void LateUpdate()
        {
            foreach (var list in unitMap.Values)
            {
                foreach (var unit in list)
                {
                    unit.LateUpdate();
                }
            }
        }

        public EnvironmentUnit CreateEnvironmentUnit(MapObstacleData data)
        {
            var unit = BattleFactory.Instance.CreateEnvironmentUnit();
            var unitId = environmentUnitId;
            var team = data.selfTeam;
            environmentUnitId++;
            unit.unitId = unitId;
            unit.team = team;
            unit.Init(data);
            AddUnitToTeam(team,unit);
            unitIdMap.Add(unitId,unit);
            return unit;
        }

        public NpcUnit CreateNpcUnit(int npcId,Vector3 position,int team = 3)
        {
            var unit = BattleFactory.Instance.CreateNpcUnit();
            var unitId = environmentUnitId;
            environmentUnitId++;
            unit.unitId = unitId;
            unit.heroId = npcId;
            unit.team = team;
            unit.position = position;
            unit.Initialize();
            if (unitHpChangeCallbacks.TryGetValue(npcId, out var action))
            {
                unit.onHpChange = action;
            }

            if (unitPositionChangeCallbacks.TryGetValue(npcId, out var positionAction))
            {
                unit.onPositionChange = positionAction;
            }
            AddUnitToTeam(team,unit);
            unitIdMap.Add(unitId,unit);
            unit.onHpChange?.Invoke();
            return unit;
        }
        
        public NpcUnit CreateTruckUnit(int npcId,Vector3 position)
        {
            var unit = BattleFactory.Instance.CreateTruckUnit();
            var unitId = environmentUnitId;
            var team = 0;
            environmentUnitId++;
            unit.unitId = unitId;
            unit.heroId = npcId;
            unit.team = team;
            unit.position = position;
            unit.Initialize();
            if (unitHpChangeCallbacks.TryGetValue(npcId, out var action))
            {
                unit.onHpChange = action;
            }

            if (unitPositionChangeCallbacks.TryGetValue(npcId, out var positionAction))
            {
                unit.onPositionChange = positionAction;
            }
            AddUnitToTeam(team,unit);
            unitIdMap.Add(unitId,unit);
            unit.onHpChange?.Invoke();
            
            if (unit.npcConfig != null && unit.npcConfig.bubble.Count > 0)
            {
                HeroEventBubbleManager.Instance.RegisterHero(unitId, unit.npcConfig.bubble);
            }
            return unit;
        }
        
        public BattleUnit CreateBattleUnit(int team,int heroId,Vector3 position = default,Vector3 forward = default)
        {
            if (forward == default)
            {
                forward = Vector3.forward;
            }
            //BattleDebug.LogError("刷怪" + position);
            var battleUnit = BattleFactory.Instance.CreateBattleUnit();
            var unitId = 0;
            if (team == 0)
            {
                unitId = selfUnitId;
                selfUnitId++;
            }
            else
            {
                unitId = enemyUnitId;
                enemyUnitId++;
            }
            battleUnit.unitId = unitId;
            battleUnit.team = team;
            battleUnit.heroId = heroId;
            battleUnit.position = position;
            battleUnit.Forward = forward;
            if (unitHpChangeCallbacks.TryGetValue(heroId, out var action))
            {
                battleUnit.onHpChange = action;
            }

            if (unitPositionChangeCallbacks.TryGetValue(heroId, out var positionAction))
            {
                battleUnit.onPositionChange = positionAction;
            }
            battleUnit.Initialize();
            AddUnitToTeam(team,battleUnit);
            unitIdMap.Add(unitId,battleUnit);
            battleUnit.onHpChange?.Invoke();
            if (battleUnit.config.bubble != null && battleUnit.config.bubble.Count > 0)
            {
                HeroEventBubbleManager.Instance.RegisterHero(unitId, battleUnit.config.bubble);
            }
            battleUnit.Forward = forward;
            return battleUnit;
        }

        public BattleUnit CreateSummonUnit(BattleUnit parent, int summonId)
        {
            var summonConfig = BattleConfigManager.Instance.GetSummonConfig(summonId);
            if (summonConfig == null)
            {
                return null;
            }
            var pos = summonConfig.positions[0];
            var offset = new Vector3(pos.x / 1000f, pos.y / 1000f, pos.z / 1000f);
            var position = parent.position + Quaternion.AngleAxis(PveUtils.GetAngle(Vector3.forward,parent.Forward),Vector3.up) * offset;
            var battleUnit = CreateBattleUnit(parent.team, summonConfig.hero_id, position);
            battleUnit.isSummonUnit = true;
            battleUnit.summonOffset = offset;
            battleUnit.summonParent = parent;
            battleUnit.currentHp = parent.totalHp;
            battleUnit.totalHp = parent.totalHp;
            battleUnit.attack = parent.attack;
            battleUnit.defence = parent.defence;
            if (parent.team == 0)
            {
                battleUnit.SetNavmeshAgentActive(false);
                battleUnit.canBeTarget = false;
                battleUnit.RemoveCollider();
            }
            return battleUnit;
        }

        private void AddUnitToTeam(int team, BattleUnit unit)
        {
            if (!unitMap.TryGetValue(team, out var list))
            {
                list = new List<BattleUnit>();
                unitMap.Add(team, list);
            }
            unit.indexInUnitManager = list.Count;
            list.Add(unit);
        }
        
        private void RemoveUnitFromTeam(int team, BattleUnit unit)
        {
            if (unitMap.TryGetValue(team, out var list))
            {
                var index = unit.indexInUnitManager;
                var lastIndex = list.Count - 1;
                if (index < lastIndex)
                {
                    var last = list[lastIndex];
                    last.indexInUnitManager = index;
                    list[index] = last;
                }
                list.RemoveAt(lastIndex);
            }
        }

        public BattleUnit SearchTarget(BattleUnit source, int teamMask, EBattleSkillSearchSort searchSort, float searchRange,
            List<int> banList = null)
        {
            BattleUnit candidate = null;
            float minValue = float.MaxValue;
            float maxValue = float.MinValue;
            for (int team = 0; team <= 2; team++)
            {
                if (BattleHelper.IsTeamMatch(team, teamMask))
                {
                    if (unitMap.TryGetValue(team, out var list) && list.Count > 0)
                    {
                        for (int i = 0; i < list.Count; i++)
                        {
                            if (list[i].isDead || list[i].locked || !list[i].canBeTarget)
                            {
                                continue;
                            }
                            if (banList != null && banList.Contains(list[i].unitId))
                            {
                                continue;
                            }
                            var distance = Vector3.Distance(source.Position, list[i].Position);
                            if (searchRange >= distance)
                            {
                                //开始比较
                                if (searchSort == EBattleSkillSearchSort.Near)
                                {
                                    if (candidate == null || distance < minValue)
                                    {
                                        minValue = distance;
                                        candidate = list[i];
                                    }
                                }
                                else if (searchSort == EBattleSkillSearchSort.Far)
                                {
                                    if (candidate == null || distance > minValue)
                                    {
                                        minValue = distance;
                                        candidate = list[i];
                                    }
                                }
                                else if (searchSort == EBattleSkillSearchSort.HpPercentLow)
                                {
                                    var hpPercent = list[i].currentHp / list[i].totalHp;
                                    if (candidate == null || hpPercent < minValue)
                                    {
                                        minValue = hpPercent;
                                        candidate = list[i];
                                    }
                                }
                                else if (searchSort == EBattleSkillSearchSort.HpPercentHigh)
                                {
                                    var hpPercent = list[i].currentHp / list[i].totalHp;
                                    if (candidate == null || hpPercent > maxValue)
                                    {
                                        minValue = hpPercent;
                                        candidate = list[i];
                                    }
                                }
                                else if (searchSort == EBattleSkillSearchSort.Random)
                                {
                                    var randomValue = UnityEngine.Random.Range(0, 1f);
                                    if (candidate == null || randomValue < minValue)
                                    {
                                        minValue = randomValue;
                                        candidate = list[i];
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return candidate;
        }
        public List<BattleUnit> GetTargetsByCircle(Vector3 center,float radius,int team)
        {
            List<BattleUnit> targets = new List<BattleUnit>();
            if (unitMap.TryGetValue(team, out var list) && list.Count > 0)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    if (list[i].isDead)
                    {
                        continue;
                    }
                    var distance = Vector3.Distance(center, list[i].Position);
                    if (radius >= distance)
                    {
                        targets.Add(list[i]);
                    }
                }
            }
            return targets;
        }
        
        public BattleUnit GetUnitById(int unitId)
        {
            if (unitIdMap.TryGetValue(unitId, out var unit))
            {
                return unit;
            }
            UnityEngine.Debug.LogError("找不到Unit "+ unitId);
            return null;
        }

        public BattleUnit GetUnitByHeroId(int heroId)
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    if (list[i].heroId == heroId && !list[i].isDead)
                    {
                        return list[i];
                    }
                }
            }
            return null;
        }

        public void UnitDead(BattleUnit unit)
        {
            if (unit.config != null && unit.config.drop_basemoney > 0)
            {
                //掉落道具
                BattlePropsManager.Instance.CreateDropDollar(unit.Position,-unit.Forward,unit.config.drop_basemoney);
                BattlePropsManager.Instance.CheckoutUnitDrop(unit.heroId,unit.Position,-unit.Forward);
            }
            BattleEventManager.Instance.OnUnitDead(unit);
            MissionManager.Instance.OnKill(unit.team,unit.heroId);
            if (unit.team == 0)
            {
                StageManager.Instance.TeamGroup.RemoveUnitFromTeam(unit);
            }
            HeroEventBubbleManager.Instance.UnRegisterHero(unit.unitId);
        }

        public void UnitKill(BattleUnit att, BattleUnit target)
        {
            if (att.team == 0)
            {
                HeroEventBubbleManager.Instance.Kill(att.unitId, target.unitId);
            }
        }

        public void RemoveUnit(BattleUnit unit)
        {
            var team = unit.team;
            if (unitMap.TryGetValue(team,out var list))
            {
                var index = unit.indexInUnitManager;
                var lastIndex = list.Count - 1;
                if (index < lastIndex)
                {
                    var last = list[lastIndex];
                    last.indexInUnitManager = index;
                    list[index] = last;
                }
                list.RemoveAt(lastIndex);
            }
            unitMap.Remove(unit.unitId);
            BattleEventManager.Instance.OnUnitRemoved(unit);
            unit.Dispose();
            //ClassManager.Free(unit);
        }

        public void OnMapReady()
        {
            foreach(var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].InitNavmeshComponent();
                }
            }
        }
        
        public void DisableAgent()
        {
            foreach(var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].DisableAgent();
                }
            }
        }
        
        public void EnemyStartSearch()
        {
            if (unitMap.TryGetValue(2, out var list))
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].InitAI(EEnemyAIType.SearchAllMap);
                    list[i].SearchTarget();
                    list[i].ShowBubble();
                }
            }
        }

        public void SetRenderEnabled(bool enabled)
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].SetRenderEnabled(enabled);
                }
            }
        }

        public void SetNavmeshEnabled(bool enabled)
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].SetNavmeshAgentActive(enabled);
                }
            }
        }

        public void OnLightElementChange()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].OnLightElementChange();
                }
            }
        }

        private Dictionary<int,Dictionary<int,BattleUnit>> enemyGroupMap = new Dictionary<int, Dictionary<int,BattleUnit>>();
        private int groupId = 1;
        
        public void CreateEnemyGroup(BattleMapMonsterGroup group)
        {
            if (!enemyGroupMap.TryGetValue(groupId, out var map))
            {
                map = new Dictionary<int, BattleUnit>();
                enemyGroupMap.Add(groupId,map);
            }
            for (int i = 0; i < group.monsters.Count; i++)
            {
                var monster = group.monsters[i];
                BattleUnit unit = null;
                if (monster.unitType == EMapUnitType.Npc)
                {
                    unit = CreateNpcUnit(monster.heroId,monster.position,monster.team);
                    (unit as NpcUnit).pointId = monster.pointId;
                }
                else if (monster.unitType == EMapUnitType.Teammate)
                {
                    unit = CreateBattleUnit(3, monster.heroId,monster.position,monster.forward);
                }
                else if (monster.unitType == EMapUnitType.NpcRescue)
                {
                    unit = CreateNpcUnit(monster.heroId,monster.position);
                    (unit as NpcUnit).pointId = monster.pointId;
                    (unit as NpcUnit).InitRescue();
                }
                else if (monster.unitType == EMapUnitType.Truck)
                {
                    unit = CreateTruckUnit(monster.heroId,monster.position);
                    int.TryParse(monster.extraParam1, out var routeId);
                    (unit as TruckUnit).InitTruck(routeId);
                }
                else
                {
                    unit = CreateBattleUnit(2, monster.heroId,monster.position,monster.forward);
                }
                unit.InitAI(monster.aiType,monster.searchRange,monster.transmitToGroup);
                unit.specialIndex = monster.specialIndex;
                unit.Forward = monster.forward;
                unit.TargetForward = monster.forward;
                unit.enemyGroupId = groupId;
                map.Add(unit.unitId,unit);
                unit.fogId = group.fogId;
                unit.locked = StageManager.Instance.IsFogLocked(group.fogId);
                var attributeRatio = monster.attributeRatio;
                if (attributeRatio > 0)
                {
                    unit.attack = Mathf.CeilToInt(unit.attack * attributeRatio);
                    unit.defence = Mathf.CeilToInt(unit.defence * attributeRatio);
                    unit.totalHp = Mathf.CeilToInt(unit.totalHp * attributeRatio);
                    unit.currentHp = Mathf.CeilToInt(unit.currentHp * attributeRatio);
                }
                //enemy.aiConfig = BattleConfigManager.Instance.GetEnemyAIConfig(1);
            }
            groupId++;
        }

        public void SetTargetToGroup(int groupId, BattleUnit target)
        {
            if (enemyGroupMap.TryGetValue(groupId, out var map))
            {
                foreach (var unit in map.Values)
                {
                    //BattleDebug.LogError("被通知设置目标 " + unit.unitId);
                    unit.target = target;
                    unit.ShowBubble();
                }
            }
        }

        public Vector3 battlePoint;
        
        public void SetBattlePoint(Vector3 battlePoint)
        {
            this.battlePoint = battlePoint;
        }

        public BattleUnit GetUnitByTeam(int team)
        {
            if (unitMap.TryGetValue(team, out var list))
            {
                for (int i = 0; i < list.Count; i++)
                {
                    return list[i];
                }
            }
            return null;
        }

        public void OnFogUnlocked(int fogId)
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    if (list[i].fogId == fogId)
                    {
                        list[i].locked = false;
                    }
                }
            }
        }

        public void OnCameraChanged()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].OnCameraChanged();
                }
            }
        }

        public BattleUnit GetUnitByTeamAndIndex(int team, int specialIndex)
        {
            if (unitMap.TryGetValue(team,out var list))
            {
                if (team == 0)
                {
                    if (list.Count > specialIndex)
                    {
                        return list[specialIndex];
                    }
                }
                else
                {
                    for (int i = 0; i < list.Count; i++)
                    {
                        if (list[i].specialIndex == specialIndex)
                        {
                            return list[i];
                        }
                    }
                }
            }
            BattleDebug.LogError("特殊单位找不到 " + team + " " + specialIndex);
            return null;
        }

        public void JoinTeam(BattleUnit unit)
        {
            if (BattleDebug.enableLog)
            {
                BattleDebug.LogError("加入队伍 " + unit.unitId);
            }
            var team = unit.team;
            RemoveUnitFromTeam(team,unit);
            unit.team = 0;
            AddUnitToTeam(0,unit);
            var insertToFront = unit is NpcUnit;
            StageManager.Instance.TeamGroup.AddMember(unit, false,insertToFront);
            if (unit is NpcUnit npc)
            {
                npc.OnRescued();
            }
        }

        public void OnPause()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].OnPause();
                }
            }
        }

        public void OnResume()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].OnResume();
                }
            }
        }

        public void EnemyMpMax()
        {
            if (unitMap.TryGetValue(2, out var list))
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].SetMpChange(list[i].totalMp);
                }
            }
        }
        
        public void AddUnitHpChangeCallback(int heroId, Action callback)
        {
            unitHpChangeCallbacks[heroId] = callback;
        }
        
        public void AddUnitPositionChangeCallback(int heroId, Action callback)
        {
            unitHpChangeCallbacks[heroId] = callback;
        }

        public void UpdateShadowOffset()
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].UpdateShadowOffset();
                }
            }
        }

        public void SetHudActive(bool active)
        {
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    list[i].SetShowHud(active);
                }
            }
        }
        
        public override void Dispose()
        {
            unitHpChangeCallbacks.Clear();
            unitPositionChangeCallbacks.Clear();
            foreach (var list in unitMap.Values)
            {
                for (int i = 0; i < list.Count; i++)
                {
                    var unit = list[i];
                    unit.Dispose();
                    ClassManager.Free(unit);
                }
            }
            unitMap.Clear();
            unitIdMap.Clear();
            enemyGroupMap.Clear();
            groupId = 1;
            selfUnitId = 1000;
            enemyUnitId = 2000;
            environmentUnitId = 3000;
        }
    }
}
